package LDraw.Files; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.StringTokenizer; import java.util.TreeSet; import javax.media.opengl.GL2; import javax.swing.undo.UndoManager; import Command.LDrawColorT; import Command.LDrawLSynthDirective; import Command.LDrawLine; import Command.LDrawPart; import Command.LDrawQuadrilateral; import Command.LDrawTriangle; import Common.Box2; import Common.Box3; import Common.Matrix3; import Common.Matrix4; import Common.Ray3; import Common.Vector2f; import Common.Vector3f; import LDraw.Support.ColorLibrary; import LDraw.Support.DispatchGroup; import LDraw.Support.LDrawDirective; import LDraw.Support.LDrawKeywords; import LDraw.Support.LDrawUtilities; import LDraw.Support.MatrixMath; import LDraw.Support.Range; import LDraw.Support.RangeException; import LDraw.Support.type.CacheFlagsT; import LDraw.Support.type.MessageT; import LDraw.Support.type.ViewOrientationT; import Notification.NotificationCenter; import Notification.NotificationMessageT; import Renderer.ILDrawCollector; import Renderer.ILDrawDLHandle; import Renderer.ILDrawRenderer; import Renderer.LDrawDL; import Renderer.LDrawDLCleanup_f; //============================================================================== // //File: LDrawModel // //Purpose: Represents a collection of Lego bricks that form a single model. // //Created by Allen Smith on 2/19/05. //Copyright (c) 2005. All rights reserved. //============================================================================== /** * @Class LDrawModel * * @Purpose Represents a collection of Lego bricks that form a single model. * @Represent LDrarwModel.(h, m) of Bricksmith * * @author funface2 * @since 2014-03-13 * */ // ============================================================================== // // File: LDrawModel.h // // Purpose: Represents a collection of Lego bricks that form a single model. // // Bricksmith imposes an arbitrary requirement that a model be // composed of a series of steps. Each model must have at least one // step in it, and only LDrawSteps can be put into the model's // subdirective array. Each LDraw model contains at least one step // even if it contains no 0 STEP commands, since the final step in // the model is not required to have step marker. // // Created by Allen Smith on 2/19/05. // Copyright (c) 2005. All rights reserved. // ============================================================================== public class LDrawModel extends LDrawContainer implements Cloneable { /** * @uml.property name="modelDescription" */ String modelDescription; /** * @uml.property name="fileName" */ String fileName; /** * @uml.property name="author" */ String author; /** * @uml.property name="rotationCenter" * @uml.associationEnd */ Vector3f rotationCenter; /** * @uml.property name="colorLibrary" * @uml.associationEnd */ ColorLibrary colorLibrary; // in-scope !COLOURS local to the model /** * @uml.property name="stepDisplayActive" */ boolean stepDisplayActive; // YES if we are only display steps // 1-currentStepDisplayed /** * @uml.property name="currentStepDisplayed" */ int currentStepDisplayed; // display up to and including this step index /** * @uml.property name="cachedBounds" * @uml.associationEnd */ Box3 cachedBounds; // bounds of the model - only covers steps that are // showing // steps are stored in the superclass. // Drag and Drop /** * @uml.property name="draggingDirectives" * @uml.associationEnd */ LDrawStep draggingDirectives; /** * @uml.property name="isOptimized" */ boolean isOptimized; // Were we ever structure-optimized - used to optimize // out // some drawing on library parts. /** * @uml.property name="dl" * @uml.associationEnd */ private HashMap<GL2, ILDrawDLHandle> dl = new HashMap<GL2, ILDrawDLHandle>(); // Cached // DL // if // we // have // one. /** * @uml.property name="dl_dtor" * @uml.associationEnd */ LDrawDLCleanup_f dl_dtor; public LDrawModel() { init(); } // ---------- model // ---------------------------------------------------[static]-- // // Purpose: Creates a new model ready to be edited. // // ------------------------------------------------------------------------------ public static LDrawModel model() { LDrawModel newModel = new LDrawModel(); // Then fill it up with useful initial attributes newModel.setModelDescription("UntitledModel"); newModel.setFileName(""); newModel.setAuthor(LDrawUtilities.defaultAuthor()); // Need to create a blank step. newModel.addStep(); return newModel; }// end model // ========== init // ============================================================== // // Purpose: Creates a new, completely blank model file. // // ============================================================================== public LDrawModel init() { super.init(); colorLibrary = new ColorLibrary(); cachedBounds = Box3.getInvalidBox(); setModelDescription(""); setFileName(""); setAuthor(""); rotationCenter = Vector3f.getZeroVector3f(); setStepDisplay(false); return this; }// end init // ========== initWithLines:inRange:parentGroup: // ================================ // // Purpose: Creates a new model file based on the lines from a file. // These lines of strings should only describe one model, not // multiple ones. // // This method divides the model into steps. A step may be ended // by: // * a 0 STEP line // * a 0 ROTSTEP line // * the end of the file // // A STEP or ROTSTEP command is part of the step they end, so they // are the last line IN the step. // // The final step marker is optional. Thus a file that has no step // markers still has one step. // // ============================================================================== public LDrawModel initWithLines(ArrayList<String> lines, Range range, DispatchGroup parentGroup) { int contentStartIndex = 0; Range stepRange = range; int maxLineIndex = 0; int insertIndex = 0; ArrayList<LDrawDirective> substeps = null; // Start with a nice blank model. try { super.initWithLines(lines, range, parentGroup); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cachedBounds = Box3.getInvalidBox(); substeps = new ArrayList<LDrawDirective>(); // Try and get the header out of the file. If it's there, the lines // returned // will not contain it. contentStartIndex = parseHeaderFromLines(lines, range.getLocation()); maxLineIndex = range.getMaxRange(); DispatchGroup modelDispatchGroup = null; modelDispatchGroup = new DispatchGroup(); if(parentGroup!=null) modelDispatchGroup.extendsFromParent(parentGroup); do { stepRange = LDrawStep.rangeOfDirectiveBeginningAtIndex( contentStartIndex, lines, maxLineIndex); LDrawStep newStep = new LDrawStep(); try { newStep.initWithLines(lines, stepRange, modelDispatchGroup); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } substeps.add(newStep); ++insertIndex; contentStartIndex = stepRange.getMaxRange() + 1; } while (contentStartIndex <= range.getMaxRange()); // #if USE_BLOCKS // dispatch_group_notify(modelDispatchGroup,queue, // ^{ // #endif int counter = 0; for (counter = 0; counter < insertIndex; counter++) { LDrawStep step = (LDrawStep) substeps.get(counter); addStep(step); } // Degenerate case: utterly empty file. Create one empty step, because // it is // illegal to have a 0-step model in Bricksmith. if (steps().size() == 0) { addStep(); } // #if USE_BLOCKS // if(parentGroup != NULL) // dispatch_group_leave(parentGroup); // }); // dispatch_release(modelDispatchGroup); // #endif return this; }// end initWithLines:inRange: // ========== draw:viewScale:parentColor: // ======================================= // // Purpose: Simply draw all the steps; they will worry about drawing all // their constituents. // // ============================================================================== public void collectColor() { ArrayList<LDrawDirective> steps = subdirectives(); int maxIndex = maxStepIndexToOutput(steps); LDrawStep currentDirective = null; int counter = 0; // Draw all the steps in the model for (counter = 0; counter <= maxIndex; counter++) { currentDirective = (LDrawStep) steps.get(counter); currentDirective.collectColor(); } }// end draw:viewScale:parentColor: // ========== drawSelf: // =========================================================== // // Purpose: Draw this directive and its subdirectives by calling APIs on // the passed in renderer, then calling drawSelf on children. // // Notes: The LDrawModel serves as the display-list holder for all // primitives directly "underneath" it. Thus when we hit drawSelf // We revalidate our DL and then just draw it. // // "Our" DL is a DL containing only the mesh primitives DIRECTLY // underneath us. Triangles that are part of a model that is // referenced by a PART underneath us are not collected - that is, // collection is not recursive. We count on the library being // flattened to ensure one VBO per library part. // // ================================================================================ public void drawSelf(GL2 gl2, ILDrawRenderer renderer) { // DL cache control: we may have to throw out our old DL if it has gone // stale. EITHER WAY we mark our DL bit as validated per the rules of // the observable protocol. if (dl.containsKey(gl2) == true) { if (revalCache(CacheFlagsT.DisplayList) == CacheFlagsT.DisplayList) { if (dl.get(gl2) != null) ((LDrawDL) (dl.get(gl2))).destroy(gl2); dl_dtor = null; dl.remove(gl2); // System.out.println("detroy DL"); } } else revalCache(CacheFlagsT.DisplayList); // Now: if we do not have a DL (no DL or we threw it out because it // was invalid) build one now: get a collector and call "collect" on // ourselves, which will walk our tree picking up primitives. if (dl.containsKey(gl2) == false) { ILDrawCollector collector = renderer.beginDL(); collectSelf(collector); dl.put(gl2, renderer.endDL(gl2, dl_dtor)); // System.out.println("create DL"); } // Finally: if we have a DL (cached or brand new, draw it!!) if (dl.get(gl2) != null){ renderer.drawDL(gl2, dl.get(gl2)); } if (!isOptimized) { // Slow stuff part 1, skipped on library parts for speed. // First: recurse the 'drawSelf message. This is needed for: // - Parts, which draw, not collect and // - Drag handles for selected primitives. // Library parts are guaranteed to be only steps of primitives, // so there is no need for this. ArrayList<LDrawDirective> steps = subdirectives(); int maxIndex = maxStepIndexToOutput(steps); LDrawDirective currentDirective = null; int counter = 0; for (counter = 0; counter <= maxIndex; counter++) { currentDirective = steps.get(counter); currentDirective.drawSelf(gl2, renderer); } } }// drawSelf: // ========== collectSelf: // ======================================================== // // Purpose: Collect self is called on each directive by its parents to // accumulate _mesh_ data into a display list for later drawing. // The collector protocol passed in is some object capable of // remembering the collectable data. // // Models simply recurse to their steps. // // Notes: We do NOT revalidate our display list, because we do not expect // to hit this case from a 'parent'. Rather, we expect a part to // call "draw" on us (a model) and then we bulid our OWN DL. // // See drawSelf: implementation above for cached DL handling! // // ================================================================================ public void collectSelf(ILDrawCollector renderer) { ArrayList<LDrawDirective> steps = subdirectives(); int maxIndex = maxStepIndexToOutput(steps); LDrawDirective currentDirective = null; int counter = 0; // Draw all the steps in the model for (counter = 0; counter <= maxIndex; counter++) { currentDirective = steps.get(counter); currentDirective.collectSelf(renderer); } }// end collectSelf: // ========== debugDrawboundingBox // ============================================== // // Purpose: Draw a translucent visualization of our bounding box to test // bounding box caching. // // ============================================================================== public void debugDrawboundingBox(GL2 gl2) { ArrayList<LDrawDirective> steps = subdirectives(); int maxIndex = maxStepIndexToOutput(steps); LDrawDirective currentDirective = null; int counter = 0; // Draw all the steps in the model for (counter = 0; counter <= maxIndex; counter++) { currentDirective = steps.get(counter); currentDirective.debugDrawboundingBox(gl2); } super.debugDrawboundingBox(gl2); }// end debugDrawboundingBox public void getRange(Matrix4 transform, float[] range) { ArrayList<LDrawDirective> steps = subdirectives(); int maxIndex = maxStepIndexToOutput(steps); LDrawStep currentDirective = null; int counter = 0; // Draw all the steps in the model for (counter = 0; counter <= maxIndex; counter++) { currentDirective = (LDrawStep) steps.get(counter); currentDirective.getRange(transform, range); } } // ========== hitTest:transform:viewScale:boundsOnly:creditObject:hits: // ======= // // Purpose: Hit-test the geometry. // // ============================================================================== public void hitTest(Ray3 pickRay, Matrix4 transform, LDrawDirective creditObject, HashMap<LDrawDirective, Float> hits) { ArrayList<LDrawDirective> steps = subdirectives(); int maxIndex = maxStepIndexToOutput(steps); LDrawStep currentDirective = null; int counter = 0; // Draw all the steps in the model for (counter = 0; counter <= maxIndex; counter++) { currentDirective = (LDrawStep) steps.get(counter); currentDirective.hitTest(pickRay, transform, creditObject, hits); } } // ========== boxTest:transform:boundsOnly:creditObject:hits: // =================== // // Purpose: Check for intersections with screen-space geometry. // // ============================================================================== public boolean boxTest(Box2 bounds, Matrix4 transform, boolean boundsOnly, LDrawDirective creditObject, TreeSet<LDrawDirective> hits) { if (!MatrixMath .VolumeCanIntersectBox(boundingBox3(), transform, bounds)) { return false; } ArrayList<LDrawDirective> steps = subdirectives(); int maxIndex = maxStepIndexToOutput(steps); LDrawStep currentDirective = null; int counter = 0; // Draw all the steps in the model for (counter = 0; counter <= maxIndex; counter++) { currentDirective = (LDrawStep) steps.get(counter); if (currentDirective.boxTest(bounds, transform, boundsOnly, creditObject, hits)) if (creditObject != null) return true; } return false; }// end boxTest:transform:boundsOnly:creditObject:hits: // ========== // depthTest:inBox:transform:creditObject:bestObject:bestDepth:======= // // Purpose: depthTest finds the closest primitive (in screen space) // overlapping a given point, as well as its device coordinate // depth. // // ============================================================================== public void depthTest(Vector2f testPt, Box2 bounds, Matrix4 transform, LDrawDirective creditObject, ArrayList<LDrawDirective> bestObject, FloatBuffer bestDepth) { if (!MatrixMath.VolumeCanIntersectPoint(boundingBox3(), transform, bounds, bestDepth.get(0))) { return; } ArrayList<LDrawDirective> steps = subdirectives(); int maxIndex = maxStepIndexToOutput(steps); LDrawStep currentDirective = null; int counter = 0; // Draw all the steps in the model for (counter = 0; counter <= maxIndex; counter++) { currentDirective = (LDrawStep) steps.get(counter); currentDirective.depthTest(testPt, bounds, transform, creditObject, bestObject, bestDepth); } }// end depthTest:inBox:transform:creditObject:bestObject:bestDepth: // ========== write // ============================================================= // // Purpose: Writes out the MPD submodel, wrapped in the MPD file // commands. // // ============================================================================== public String write() { String written = new String(); String CRLF = "\r\n";// we need a DOS line-end marker, // because // LDraw is predominantly DOS-based. ArrayList<LDrawDirective> steps = subdirectives(); int numberSteps = steps.size(); LDrawStep currentStep = null; String stepOutput = null; int counter = 0; // Write out the file header in all of its irritating glory. written = written.concat(String.format("0 %s%s", modelDescription(), CRLF)); written = written.concat(String.format("0 %s %s%s", LDrawKeywords.LDRAW_HEADER_NAME, fileName(), CRLF)); written = written.concat(String.format("0 %s %s%s", LDrawKeywords.LDRAW_HEADER_AUTHOR, author(), CRLF)); // Write out all the steps in the file. for (counter = 0; counter < numberSteps; counter++) { currentStep = (LDrawStep) steps.get(counter); // Omit the 0 STEP command for 1-step models, which probably aren't // being built with steps in mind anyway. if (numberSteps == 1) stepOutput = currentStep.writeWithStepCommand(false); else stepOutput = currentStep.write(); written = written.concat(String.format("%s%s", stepOutput, CRLF)); } // Now remove that last CRLF. return written.substring(0, written.length() - CRLF.length()); }// end write // // // #pragma mark - // #pragma mark DISPLAY // #pragma mark - // // ========== browsingDescription // =============================================== // // Purpose: Returns a representation of the directive as a short // string // which can be presented to the user. // // ============================================================================== public String browsingDescription() { return modelDescription(); }// end browsingDescription // ========== iconName // ========================================================== // // Purpose: Returns the name of image file used to display this kind // of // object, or nil if there is no icon. // // ============================================================================== public String iconName() { return "Document"; }// end iconName // #pragma mark - // #pragma mark ACCESSORS // #pragma mark - // // ========== boundingBox3 // ====================================================== // // Purpose: Returns the minimum and maximum points of the box which // perfectly contains this object. // // We optimize this calculation on models whose dimensions are // known to be constant--parts from the library, for instance. // // ============================================================================== public Box3 boundingBox3() { Box3 totalBounds = Box3.getInvalidBox(); Box3 draggingBounds = Box3.getInvalidBox(); if (revalCache(CacheFlagsT.CacheFlagBounds) == CacheFlagsT.CacheFlagBounds) { cachedBounds = Box3.getInvalidBox(); ArrayList<LDrawDirective> steps = subdirectives(); int maxIndex = maxStepIndexToOutput(steps); LDrawDirective currentDirective = null; int counter = 0; // Draw all the steps in the model for (counter = 0; counter <= maxIndex; counter++) { currentDirective = steps.get(counter); cachedBounds = MatrixMath.V3UnionBox(cachedBounds, currentDirective.boundingBox3()); } } totalBounds = cachedBounds; // If drag-and-drop objects are present, add them into the bounds. if (draggingDirectives != null) { draggingBounds = LDrawUtilities .boundingBox3ForDirectives(draggingDirectives .subdirectives()); totalBounds = MatrixMath.V3UnionBox(draggingBounds, totalBounds); } return totalBounds; }// end boundingBox3 // ========== category // ========================================================== // // Purpose: Returns the category to which this model belongs. This is // determined from the description field, which is the first line // of the file for non-MPD documents. For instance: // // 0 Brick 2 x 4 // // This part would be in the category "Brick", and has the // description "Brick 2 x 4". // // ============================================================================== public String category() { String category = null; int firstSpace; // range of the category string in the first line. // The category name is the first word in the description. firstSpace = modelDescription.indexOf(" "); if (firstSpace != -1) category = modelDescription.substring(0, firstSpace); else category = modelDescription; // Clean category name of any weird notational marks if (category.charAt(0) == '_' || category.charAt(0) == '~') category = category.substring(1); return category; }// end category // ========== colorLibrary // ====================================================== // // Purpose: Returns the color library object which accumulates the // !COLOURS // defined locally within the model. // // Notes: According to the LDraw color spec, local colors having // scoping: // they become active at the point of definition and fall out of // scope at the end of the model. As a convenience in Bricksmith, // the color library will still contain all the local model colors // after a draw is complete--the library will not be purged just // for scoping's sake. It may be purged at the beginning of // drawing, however. // // ============================================================================== public ColorLibrary colorLibrary() { return colorLibrary; }// end colorLibrary // ========== draggingDirectives // ================================================ // // Purpose: Returns the objects that are currently being displayed as // part // of drag-and-drop. // // ============================================================================== public ArrayList<LDrawDirective> draggingDirectives() { if (draggingDirectives == null) return null; return draggingDirectives.subdirectives(); }// end draggingDirectives // ========== enclosingFile // ===================================================== // // Purpose: Returns the file in which this model is stored. // // ============================================================================== public LDrawFile enclosingFile() { return (LDrawFile) enclosingDirective; }// end enclosingFile // ========== modelDescription // ================================================== // // Purpose: Returns the model description, which is the first line of // the // model. (i.e., Brick 2 x 4) // // ============================================================================== public String modelDescription() { return modelDescription; }// end modelDescription // ========== fileName // ========================================================== // // Purpose: Returns the name the model is ostensibly saved under in // the // file system. // // ============================================================================== public String fileName() { return fileName; }// end fileName // ========== author // ============================================================ // // Purpose: Returns the person who created the document. // // ============================================================================== public String author() { return author; }// end author // ========== maximumStepIndexDisplayed // ========================================= // // Purpose: Returns the index of the last step which will be drawn. // The // value only has meaning if the model is in step-display mode. // // ============================================================================== public int maximumStepIndexForStepDisplay() { return currentStepDisplayed; }// end maximumStepIndexDisplayed // ========== rotationAngleForStepAtIndex: // ====================================== // // Purpose: Returns the viewing angle which should be used when // displaying // the given step in Step Display mode. // // Notes: Rotations are NOT built up in a stack. One would think End // Rotation removes an item from the stack, but actually it // restores the default view. Each step rotation completely // replaces the previous rotation, although computing the new value // may require consulting the value about to be replaced. // // The actual rotation to use is whatever we get by calculating all // the rotations up to and including the specified step. // // Neither the step, the model, nor any other data-level class is // responsible for enforcing this angle when drawing. It is up to // the document to enforce or ignore the step rotation angle. In // Bricksmith, the document only sets the step rotation when in // Step Display mode, when the step being viewed is changed. // // ============================================================================== public Vector3f rotationAngleForStepAtIndex(int stepNumber) { ArrayList<LDrawStep> steps = steps(); LDrawStep currentStep = null; LDrawStepRotationT rotationType = LDrawStepRotationT.LDrawStepRotationNone; Vector3f stepRotationAngle = Vector3f.getZeroVector3f(); Vector3f previousRotation = Vector3f.getZeroVector3f(); Vector3f newRotation = Vector3f.getZeroVector3f(); Vector3f totalRotation = Vector3f.getZeroVector3f(); Matrix4 rotationMatrix = Matrix4.getIdentityMatrix4(); int counter = 0; // Start with the default 3D angle onto the stack. If no rotation is // ever // specified, that is the one we use. newRotation = LDrawUtilities .angleForViewOrientation(ViewOrientationT.ViewOrientation3D); totalRotation = newRotation; // Build the rotation stack for (counter = 0; counter <= stepNumber && counter < steps.size(); counter++) { currentStep = (LDrawStep) steps.get(counter); rotationType = currentStep.stepRotationType(); stepRotationAngle = currentStep.rotationAngle(); switch (rotationType) { case LDrawStepRotationNone: // Nothing to do here. This means "use whatever was on the stack // last." newRotation = totalRotation; break; case LDrawStepRotationRelative: // Start with the default 3D rotation previousRotation = LDrawUtilities .angleForViewOrientation(ViewOrientationT.ViewOrientation3D); // Add the new value to it. rotationMatrix = MatrixMath.Matrix4Rotate( Matrix4.getIdentityMatrix4(), stepRotationAngle); rotationMatrix = MatrixMath.Matrix4Rotate(rotationMatrix, previousRotation); newRotation = MatrixMath .Matrix4DecomposeXYZRotation(rotationMatrix); // convert from radians to degrees newRotation.setX((float) Math.toDegrees(newRotation.getX())); newRotation.setY((float) Math.toDegrees(newRotation.getY())); newRotation.setZ((float) Math.toDegrees(newRotation.getZ())); break; case LDrawStepRotationAbsolute: // Use the step's angle directly newRotation = stepRotationAngle; break; case LDrawStepRotationAdditive: // Peek at the previous rotation on the stack previousRotation = totalRotation; // Add the new value to it. rotationMatrix = MatrixMath.Matrix4Rotate( Matrix4.getIdentityMatrix4(), stepRotationAngle); rotationMatrix = MatrixMath.Matrix4Rotate(rotationMatrix, previousRotation); newRotation = MatrixMath .Matrix4DecomposeXYZRotation(rotationMatrix); // convert from radians to degrees newRotation.setX((float) Math.toDegrees(newRotation.getX())); newRotation.setY((float) Math.toDegrees(newRotation.getY())); newRotation.setZ((float) Math.toDegrees(newRotation.getZ())); break; case LDrawStepRotationEnd: // This means end all rotations and restore the default angle. // It's not a stack. Bizarre. newRotation = LDrawUtilities .angleForViewOrientation(ViewOrientationT.ViewOrientation3D); break; } // Replace the cumulative rotation with the newly-computed one totalRotation = newRotation; } // Return the final calculated angle. This is the absolute rotation // to // which // we are to set the view. return totalRotation; }// end rotationAngleForStepAtIndex: // ========== rotationCenter // ==================================================== // ============================================================================== public Vector3f rotationCenter() { return rotationCenter; } // ========== stepDisplay // ======================================================= // // Purpose: Returns YES if the receiver only displays the steps // through the index of the currentStepDisplayed instance variable. // // ============================================================================== public boolean stepDisplay() { return stepDisplayActive; }// end stepDisplay // ========== steps // ============================================================= // // Purpose: Returns the steps which constitute this model. // // ============================================================================== public ArrayList<LDrawStep> steps() { ArrayList<LDrawStep> steps = new ArrayList<LDrawStep>(); for (LDrawDirective item : subdirectives()) steps.add((LDrawStep) item); return steps; }// end steps // ========== visibleStep // ======================================================= // // Purpose: Returns the last step which would be drawn if this model // were // drawn right now. // // ============================================================================== public LDrawStep visibleStep() { ArrayList<LDrawStep> steps = steps(); LDrawStep lastStep = null; if (stepDisplay() == true) lastStep = steps.get(maxStepIndexToOutput(subdirectives())); else lastStep = steps.get(steps.size() - 1); return lastStep; }// end visibleStep // // // #pragma mark - // // ========== setDraggingDirectives: // ============================================ // // Purpose: Sets the parts which are being manipulated in the model // via // drag-and-drop. // // ============================================================================== public void setDraggingDirectives(ArrayList<LDrawDirective> directives) { LDrawStep dragStep = null; LDrawDirective currentDirective = null; int counter = 0; // Remove primitives from the previous dragging directives from the // optimized vertexes if (draggingDirectives != null) { ArrayList<LDrawLine> lines = new ArrayList<LDrawLine>(); ArrayList<LDrawTriangle> triangles = new ArrayList<LDrawTriangle>(); ArrayList<LDrawQuadrilateral> quadrilaterals = new ArrayList<LDrawQuadrilateral>(); draggingDirectives.flattenIntoLines(lines, triangles, quadrilaterals, null, ColorLibrary.sharedColorLibrary() .colorForCode(LDrawColorT.LDrawCurrentColor), Matrix4.getIdentityMatrix4(), Matrix3.getIdentityMatrix3(), false); } // When we get sent nil directives, nil out the drag step. if (directives != null) { dragStep = LDrawStep.emptyStep(); // The law of Bricksmith is that all parts in a model must be // enclosed in // a // step. Resistance is futile. for (counter = 0; counter < directives.size(); counter++) { currentDirective = directives.get(counter); dragStep.addDirective(currentDirective); } // Tell the element that it lives in us now. This is important for // submodel references being dragged; without it, they have no way // of // resolving their part reference, and thus can't draw during their // drag. dragStep.setEnclosingDirective(this); // ---------- Optimize primitives // --------------------------------------- ArrayList<LDrawLine> lines = new ArrayList<LDrawLine>(); ArrayList<LDrawTriangle> triangles = new ArrayList<LDrawTriangle>(); ArrayList<LDrawQuadrilateral> quadrilaterals = new ArrayList<LDrawQuadrilateral>(); dragStep.flattenIntoLines( lines, triangles, quadrilaterals, null, ColorLibrary.sharedColorLibrary().colorForCode( LDrawColorT.LDrawCurrentColor), Matrix4.getIdentityMatrix4(), Matrix3.getIdentityMatrix3(), false); } draggingDirectives = dragStep; }// end setDraggingDirectives: // ========== setModelDescription: // ============================================== // // Purpose: Sets a new model description. // // ============================================================================== /** * @param newDescription * @uml.property name="modelDescription" */ public void setModelDescription(String newDescription) { modelDescription = newDescription; }// end setModelDescription: // ========== setFileName: // ====================================================== // // Purpose: Sets the name the model is ostensibly saved under in the // file system. This may take on a rather different meaning in // multi-part documents. It also has no real connection with the // actual filesystem name. // // ============================================================================== /** * @param newName * @uml.property name="fileName" */ public void setFileName(String newName) { fileName = newName; }// end setFileName: // ========== setAuthor: // ======================================================== // // Purpose: Changes the name of the person who created the model. // // ============================================================================== /** * @param newAuthor * @uml.property name="author" */ public void setAuthor(String newAuthor) { // LLW - Don't allow author to be set to nil, as this causes funky // behavior in the inspector if (newAuthor == null) newAuthor = ""; author = newAuthor; }// end setAuthor: // ========== setMaximumStepIndexForStepDisplay: // ================================ // // Purpose: Sets the index of the last step drawn. If the model is // not // currently in step-display mode, this call will NOT cause it to // enter step display. // // ============================================================================== public void setMaximumStepIndexForStepDisplay(int stepIndex) throws RangeException { // Need to check and make sure this step number is not overflowing the // bounds. int maximumIndex = steps().size() - 1; if (stepIndex > maximumIndex) throw new RangeException(String.format( "index (%ld) beyond maximum step index %ld", (long) stepIndex, (long) maximumIndex)); else { invalCache(CacheFlagsT.CacheFlagBounds); invalCache(CacheFlagsT.DisplayList); currentStepDisplayed = stepIndex; } }// end setMaximumStepIndexForStepDisplay: // ========== setRotationCenter: // ================================================ // // Purpose: Returns the point around which the model should be spun // while // being viewed. // // ============================================================================== /** * @param newPoint * @uml.property name="rotationCenter" */ public void setRotationCenter(Vector3f newPoint) { Vector3f oldPoint = rotationCenter; rotationCenter = newPoint; // todo // NSDictionary *info = [NSDictionary dictionaryWithObject:[NSValue // valueWithBytes:&oldPoint objCType:@encode(Point3)] // forKey:@"oldRotationCenter"]; // [[NSNotificationCenter defaultCenter] // postNotificationName:LDrawModelRotationCenterDidChangeNotification // object:[self enclosingFile] userInfo:info]; NotificationCenter .getInstance() .postNotification( NotificationMessageT.LDrawModelRotationCenterDidChange, null); } // ========== setStepDisplay // ==================================================== // // Purpose: Sets whether the receiver only displays the steps through // the index of the currentStepDisplayed instance variable. // // ============================================================================== public void setStepDisplay(boolean flag) { invalCache(CacheFlagsT.CacheFlagBounds); invalCache(CacheFlagsT.DisplayList); stepDisplayActive = flag; }// end setStepDisplay: // ========== addStep // =========================================================== // // Purpose: Creates a new blank step at the end of the model. Returns // the // new step created. // // ============================================================================== public LDrawStep addStep() { LDrawStep newStep = LDrawStep.emptyStep(); addDirective(newStep); // adds the step and tells it who it // belongs // to. return newStep; }// end addStep public LDrawStep addStep(int index) { LDrawStep newStep = LDrawStep.emptyStep(); insertDirective(newStep, index); // adds the step and tells it who it // belongs // to. return newStep; }// end addStep // ========== addStep: // ========================================================== // // Purpose: Adds newStep at the end of the model. // // ============================================================================== public void addStep(LDrawStep newStep) { addDirective(newStep); }// end addStep: // ========== makeStepVisible: // ================================================== // // Purpose: Guarantees that the given step is visible in this model. // // ============================================================================== public void makeStepVisible(LDrawStep step) { int stepIndex = indexOfDirective(step); // If we're in step display, but below this step, make it visible. if (stepIndex != -1 && stepIndex > maxStepIndexToOutput(subdirectives())) { try { setMaximumStepIndexForStepDisplay(stepIndex); } catch (RangeException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // Otherwise, we see everything, so by definition this step is // visible. }// end makeStepVisible // ========== removeDirectiveAtIndex: // =========================================== // // Purpose: Removes one directive from our container. We override // this // to find out our directive index _before_ the removal so we can // keep our current step in sync! // // ============================================================================== public void removeDirectiveAtIndex(int index) { invalCache(CacheFlagsT.CacheFlagBounds); invalCache(CacheFlagsT.DisplayList); if (index <= currentStepDisplayed && currentStepDisplayed > 0) --currentStepDisplayed; super.removeDirectiveAtIndex(index); } public void insertDirective(LDrawDirective directive, int index) { invalCache(CacheFlagsT.CacheFlagBounds); invalCache(CacheFlagsT.DisplayList); super.insertDirective(directive, index); } // ========== acceptsDroppedDirective: // ========================================== // // Purpose: Returns YES if this container will accept a directive // dropped // on // it. Explicitly excludes LDrawLSynthDirectives such as // INSIDE/OUTSIDE // and self-referencing model "parts" // // ============================================================================== public boolean acceptsDroppedDirective(LDrawDirective directive) { // explicitly disregard LSynth directives if (LDrawLSynthDirective.class.isInstance(directive)) { return false; } // explicitly disregard self-references if the dropped directive is a // model "part" else if (LDrawPart.class.isInstance(directive)) { String referenceName = ((LDrawPart) directive).referenceName(); String enclosingModelName = ""; LDrawDirective enclosingModel = enclosingModel(); if (LDrawMPDModel.class.isInstance(enclosingModel)) { enclosingModelName = ((LDrawMPDModel) enclosingModel()) .modelName(); } if (enclosingModelName == referenceName) { return false; } } return true; } // ========== maxStepIndexToOutput // ============================================== // // Purpose: Returns the index of the last step which should be // displayed. // // Notes: This is always supposed to return an index >= 0, simply // because // it is illegal for a model to have no steps in Bricksmith. // // ============================================================================== public int maxStepIndexToOutput(ArrayList<LDrawDirective> steps) { int maxStep = 0; // If step display is active, we want to display only as far as the // specified step, or the maximum step if the one specified exceeds // the // number of steps. if (stepDisplayActive == true) { maxStep = Math.min(steps.size() - 1, // subtract one to get last // step index in model. currentStepDisplayed); } else { maxStep = steps.size() - 1; } return maxStep; }// end maxStepIndexToOutput // ========== numberElements // ==================================================== // // Purpose: Returns the number of elements found in this model. // Currently // this does not recurse into MPD submodels which have been // included. // // ============================================================================== public int numberElements() { int numberElements = 0; for (LDrawStep step : steps()) numberElements += step.subdirectives().size(); return numberElements; }// end numberElements // ========== parseHeaderFromLines:beginningAtIndex: // ============================ // // Purpose: Given lines from an LDraw document, fill in the model // header // info. It should be of the following format: // // 0 7140 X-Wing Fighter // 0 Name: main.ldr // 0 Author: Tim Courtney <tim@zacktron.com> // 0 LDraw.org Official Model Repository // 0 http://www.ldraw.org/repository/official/ // // Note, however, that this information is *not* required, so it // may not be there. Consequently, the code below is a nightmarish // unmaintainable mess. // // Returns the line index of the first non-header line. // // ============================================================================== private int parseHeaderFromLines(ArrayList<String> lines, int index) { String currentLine = null; int counter = 0; boolean lineValidForHeader = false; int firstNonHeaderIndex = index; ByteBuffer payload = ByteBuffer.allocate(400); try { // First line. Should be a description of the model. currentLine = lines.get(index); if (line(currentLine, "", payload)) { byte[] strByte = new byte[payload.position()]; payload.clear(); payload.get(strByte); setModelDescription(new String(strByte)); firstNonHeaderIndex++; } // There are at least three more lines in a valid header. // Read the first four lines, and try to get the model info out of // them. lineValidForHeader = true; for (counter = firstNonHeaderIndex; counter < firstNonHeaderIndex + 3 && lineValidForHeader == true && counter < lines.size(); counter++) { currentLine = lines.get(counter); lineValidForHeader = false; // assume not, then disprove payload.clear(); // Second line. Should be file name. if (line(currentLine, LDrawKeywords.LDRAW_HEADER_NAME, payload)) { byte[] strByte = new byte[payload.position()]; payload.position(0); payload.get(strByte); setFileName(new String(strByte)); lineValidForHeader = true; } // Third line. Should be author name. else if (line(currentLine, LDrawKeywords.LDRAW_HEADER_AUTHOR, payload)) { byte[] strByte = new byte[payload.position()]; payload.position(0); payload.get(strByte); setAuthor(new String(strByte)); lineValidForHeader = true; } // Fourth line. MLCad used it as a nonstandard way of indicating // official status. Since it was nonstandard, nobody used it. else if (line(currentLine, "", payload)) { byte[] strByte = new byte[payload.position()]; payload.position(0); payload.get(strByte); String str = new String(strByte); if (str.equals("LDraw.org Official Model Repository") || str.equals("Unofficial Model")) { // Bricksmith followed MLCad spewing out this garbage // for // years. It is unnecessary. Now I am just stripping it // out // of any file I encounter. lineValidForHeader = true; } } if (lineValidForHeader == true) { firstNonHeaderIndex++; } } } catch (Exception e) { // Ran out of lines in the file. Oh well. We got what we got. e.printStackTrace(); } return firstNonHeaderIndex; }// end parseHeaderFromLines // ========== line:isValidForHeader: // ============================================ // // Purpose: Determines if the given line of LDraw is formatted to be // the // the specified field in a model header. // // ============================================================================== private boolean line(String line, String headerKey, ByteBuffer infoPtr) { String parsedField = null; boolean isValid = false; StringTokenizer strTokenizer = new StringTokenizer(line); if (strTokenizer.hasMoreElements()) parsedField = strTokenizer.nextToken(); else return isValid; if (parsedField.equals("0")) { if (headerKey.length() > 0 && strTokenizer.hasMoreTokens()) { parsedField = strTokenizer.nextToken(); isValid = parsedField.equals(headerKey); } else { isValid = true; } if (isValid) { if (infoPtr != null) { String infoStr = ""; while (strTokenizer.hasMoreTokens()) { infoStr += strTokenizer.nextToken(); if (strTokenizer.hasMoreTokens()) infoStr += " "; } infoPtr.put(infoStr.getBytes()); } } } return isValid; }// end line:isValidForHeader: // ========== optimizeStructure // ================================================= // // Purpose: Arranges the directives in such a way that the file will be // drawn faster. This method should *never* be called on files // which the user has created himself, since it reorganizes the // file contents. It is intended only for parts read from the part // library. // // To optimize, we flatten all the primitives referenced by a part // into a non-nested structure, then separate all the directives // out by the type: all triangles go in a step, all quadrilaterals // go in their own step, etc. // // Then when drawing, we need not call glBegin() each time. The // result is a speed increase of over 1000%. // // 1000%. That is not a typo. // // ============================================================================== public void optimizeStructure() { if(isOptimized==true)return; ArrayList<LDrawDirective> steps = subdirectives(); ArrayList<LDrawLine> lines = new ArrayList<LDrawLine>(); ArrayList<LDrawTriangle> triangles = new ArrayList<LDrawTriangle>(); ArrayList<LDrawQuadrilateral> quadrilaterals = new ArrayList<LDrawQuadrilateral>(); ArrayList<LDrawDirective> everythingElse = new ArrayList<LDrawDirective>(); LDrawStep linesStep = LDrawStep .emptyStepWithFlavor(LDrawStepFlavorT.LDrawStepLines); LDrawStep trianglesStep = LDrawStep .emptyStepWithFlavor(LDrawStepFlavorT.LDrawStepTriangles); LDrawStep quadrilateralsStep = LDrawStep .emptyStepWithFlavor(LDrawStepFlavorT.LDrawStepQuadrilaterals); LDrawStep everythingElseStep = LDrawStep .emptyStepWithFlavor(LDrawStepFlavorT.LDrawStepAnyDirectives); // Traverse the entire hiearchy of part references and sort out each // primitive type into a flat list. This allows staggering speed // increases. // // If we were to only sort without flattening, we would get a 100% speed // increase. But flattening and sorting yields over 1000%. flattenIntoLines( lines, triangles, quadrilaterals, everythingElse, ColorLibrary.sharedColorLibrary().colorForCode( LDrawColorT.LDrawCurrentColor), Matrix4.getIdentityMatrix4(), Matrix3.getIdentityMatrix3(), true); // Now that we have everything separated, remove the main step (it's the // one // that has the entire model in it) and . int directiveCount, counter; directiveCount = steps.size(); for (counter = (directiveCount - 1); counter >= 0; counter--) { removeDirectiveAtIndex(counter); } // Replace the original directives with the categorized steps we've // created if (lines.size() > 0) { for (LDrawDirective directive : lines) { linesStep.addDirective(directive); } addDirective(linesStep); } if (triangles.size() > 0) { for (LDrawDirective directive : triangles) { trianglesStep.addDirective(directive); } addDirective(trianglesStep); } if (quadrilaterals.size() > 0) { for (LDrawDirective directive : quadrilaterals) { quadrilateralsStep.addDirective(directive); } addDirective(quadrilateralsStep); } if (everythingElse.size() > 0 || subdirectives().size() == 0) { // Make // sure // there // is at // least // one // step // in // the // model! for (LDrawDirective directive : everythingElse) { everythingElseStep.addDirective(directive); } addDirective(everythingElseStep); } isOptimized = true; }// end optimizeStructure // ========== registerUndoActions // =============================================== // // Purpose: Registers the undo actions that are unique to this // subclass, // not to any superclass. // // ============================================================================== public void registerUndoActions(UndoManager undoManager) { super.registerUndoActions(undoManager); // todo // undoManager.prepareWithInvocationTarget(this).setAuthor(author()); // undoManager.prepareWithInvocationTarget(this).setFileName(fileName()); // undoManager.prepareWithInvocationTarget(this).setModelDescription(modelDescription()); // // undoManager.setActionName("UndoAttributesModel"); }// end registerUndoActions: public Object clone() throws CloneNotSupportedException { LDrawModel a = (LDrawModel) super.clone(); // a.rotationCenter = (Vector3f) rotationCenter.clone(); // a.colorLibrary = colorLibrary; // a.cachedBounds = (Box3) cachedBounds.clone(); // if (draggingDirectives != null) // a.draggingDirectives = (LDrawStep) draggingDirectives.clone(); return a; } public void notifyChanged(){ sendMessageToObservers(MessageT.MessageObservedChanged); } }